#include <types.h>
#include <string.h>
#include <vfs.h>
#include <inode.h>
#include <iobuf.h>
#include <stat.h>
#include <proc.h>
#include <error.h>
#include <assert.h>

static struct inode *get_cwd_nolock(void)
{
	return current->fs_struct->pwd;
}

static void set_cwd_nolock(struct inode *pwd)
{
	current->fs_struct->pwd = pwd;
}

static void lock_cfs(void)
{
	lock_fs(current->fs_struct);
}

static void unlock_cfs(void)
{
	unlock_fs(current->fs_struct);
}

/*
 * Get current directory as a inode.
 * 
 * We do not synchronize current->fs_struct->pwd, because it belongs exclusively
 * to its own process(or threads) with the holding lock.
 */
int vfs_get_curdir(struct inode **dir_store)
{
	struct inode *node;
	if ((node = get_cwd_nolock()) != NULL) {
		vop_ref_inc(node);
		*dir_store = node;
		return 0;
	}
	return -E_NOENT;
}

/*
 * Set current directory as a vnode.
 * The passed inode must in fact be a directory.
 */
int vfs_set_curdir(struct inode *dir)
{
	int ret = 0;
	lock_cfs();
	struct inode *old_dir;
	if ((old_dir = get_cwd_nolock()) != dir) {
		if (dir != NULL) {
			uint32_t type;
			if ((ret = vop_gettype(dir, &type)) != 0) {
				goto out;
			}
			if (!S_ISDIR(type)) {
				ret = -E_NOTDIR;
				goto out;
			}
			vop_ref_inc(dir);
		}
		set_cwd_nolock(dir);
		if (old_dir != NULL) {
			vop_ref_dec(old_dir);
		}
	}
out:
	unlock_cfs();
	return ret;
}

/*
 * Set current directory, as a pathname. Use vfs_lookup to translate
 * it to a inode.
 */
int vfs_chdir(char *path)
{
	int ret;
	struct inode *node;
	if ((ret = vfs_lookup(path, &node)) == 0) {
		ret = vfs_set_curdir(node);
		vop_ref_dec(node);
	}
	return ret;
}

/*
 * Get current directory, as a pathname.
 * Use vop_namefile to get the pathname.
 */
int vfs_getcwd(struct iobuf *iob)
{
	int ret;
	struct inode *node;
	if ((ret = vfs_get_curdir(&node)) != 0) {
		return ret;
	}
	/* The current dir must be a directory, and thus it is not a device. */
	assert(node->in_fs != NULL);

	const char *devname = vfs_get_devname(node->in_fs);
	if ((ret =
	     iobuf_move(iob, (char *)devname, strlen(devname), 1, NULL)) != 0) {
		goto out;
	}
	char colon = ':';
	if ((ret = iobuf_move(iob, &colon, sizeof(colon), 1, NULL)) != 0) {
		goto out;
	}
	ret = vop_namefile(node, iob);

out:
	vop_ref_dec(node);
	return ret;
}
